#!@PERL@
use strict;

use lib "../../infrastructure";
use BoxPlatform;

# Make protocol C++ classes from a protocol description file

# built in type info (values are is basic type, C++ typename)
# may get stuff added to it later if protocol uses extra types
my %translate_type_info =
(
	'int64' => [1, 'int64_t'],
	'int32' => [1, 'int32_t'],
	'int16' => [1, 'int16_t'],
	'int8' => [1, 'int8_t'],
	'bool' => [1, 'bool'],
	'string' => [0, 'std::string']
);

# built in instructions for logging various types
# may be added to
my %log_display_types = 
(
	'int64' => ['0x%llx', 'VAR'],
	'int32' => ['0x%x', 'VAR'],
	'int16' => ['0x%x', 'VAR'],
	'int8' => ['0x%x', 'VAR'],
	'bool' => ['%s', '((VAR)?"true":"false")'],
	'string' => ['%s', 'VAR.c_str()']
);

if (@ARGV != 1)
{
	die "Usage: $0 <protocol-txt-file>\n";
}

my ($file) = @ARGV;

open IN, $file or die "Can't open input file $file\n";

print "Making protocol classes from $file...\n";

my @extra_header_files;

# read attributes
my %attr;
while(<IN>)
{
	# get and clean line
	my $l = $_; $l =~ s/#.*\Z//; $l =~ s/\A\s+//; $l =~ s/\s+\Z//; next unless $l =~ m/\S/;
	
	last if $l eq 'BEGIN_OBJECTS';
	
	my ($k,$v) = split /\s+/,$l,2;
	
	if($k eq 'AddType')
	{
		add_type($v);
	}
	elsif($k eq 'ImplementLog')
	{
		# Always implement logging
	}
	elsif($k eq 'LogTypeToText')
	{
		my ($type_name,$printf_format,$arg_template) = split /\s+/,$v;
		$log_display_types{$type_name} = [$printf_format,$arg_template]
	}
	else
	{
		$attr{$k} = $v;
	}
}

sub add_type
{
	my ($protocol_name, $cpp_name, $header_file) = split /\s+/,$_[0];
	
	$translate_type_info{$protocol_name} = [0, $cpp_name];
	push @extra_header_files, $header_file if $header_file;
}

# check attributes
for(qw/Name ServerContextClass IdentString/)
{
	if(!exists $attr{$_})
	{
		die "Attribute $_ is required, but not specified\n";
	}
}

my $protocol_name = $attr{'Name'};
my ($context_class, $context_class_inc) = split /\s+/,$attr{'ServerContextClass'};
my $ident_string = $attr{'IdentString'};

my $current_cmd = '';
my %cmd_contents;
my %cmd_attributes;
my %cmd_constants;
my %cmd_id;
my @cmd_list;

# read in the command definitions
while(<IN>)
{
	# get and clean line
	my $l = $_; $l =~ s/#.*\Z//; $l =~ s/\s+\Z//; next unless $l =~ m/\S/;
	
	# definitions or new command thing?
	if($l =~ m/\A\s+/)
	{
		die "No command defined yet" if $current_cmd eq '';
	
		# definition of component
		$l =~ s/\A\s+//;
		
		my ($type,$name,$value) = split /\s+/,$l;
		if($type eq 'CONSTANT')
		{
			push @{$cmd_constants{$current_cmd}},"$name = $value"
		}
		else
		{
			push @{$cmd_contents{$current_cmd}},$type,$name;
		}
	}
	else
	{
		# new command
		my ($name,$id,@attributes) = split /\s+/,$l;
		$cmd_attributes{$name} = [@attributes];
		$cmd_id{$name} = int($id);
		$current_cmd = $name;
		push @cmd_list,$name;
	}
}

close IN;



# open files
my $filename_base = 'autogen_'.$protocol_name.'Protocol';
print "Writing $filename_base.cpp\n";
print "Writing $filename_base.h\n";
open CPP, "> $filename_base.cpp";
open H,   "> $filename_base.h";

my $guardname = uc 'AUTOGEN_'.$protocol_name.'Protocol_H';

print CPP <<__E;

// Auto-generated file -- do not edit

#include "Box.h"

#include <sstream>

#include "$filename_base.h"
#include "CollectInBufferStream.h"
#include "MemBlockStream.h"
#include "SelfFlushingStream.h"
#include "SocketStream.h"
__E

print H <<__E;
// Auto-generated file -- do not edit

#ifndef $guardname
#define $guardname

#include <cstdio>
#include <list>

#ifndef WIN32
#include <syslog.h>
#endif

#include "autogen_ConnectionException.h"
#include "Protocol.h"
#include "Message.h"
#include "SocketStream.h"

__E

# extra headers
for(@extra_header_files)
{
	print H qq@#include "$_"\n@;
}

print H <<__E;

// need utils file for the server
#include "Utils.h"

__E

my $message_base_class = "${protocol_name}ProtocolMessage";
my $objects_extra_h = '';
my $objects_extra_cpp = '';

# define the context
print H "class $context_class;\n\n";
print CPP <<__E;
#include "$context_class_inc"
#include "MemLeakFindOn.h"
__E

my $request_base_class = "${protocol_name}ProtocolRequest";
my $reply_base_class   = "${protocol_name}ProtocolReply";
# the abstract protocol interface
my $custom_protocol_subclass = $protocol_name."Protocol";
my $client_server_base_class = $protocol_name."ProtocolClientServer";
my $replyable_base_class = $protocol_name."ProtocolReplyable";
my $callable_base_class = $protocol_name."ProtocolCallable";
my $send_receive_class = $protocol_name."ProtocolSendReceive";

print H <<__E;
class $custom_protocol_subclass;
class $client_server_base_class;
class $callable_base_class;
class $replyable_base_class;

class $message_base_class : public Message
{
public:
	virtual std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol,
		$context_class &rContext) const;
	virtual std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol,
		$context_class &rContext, IOStream& rDataStream) const;
	virtual bool HasStreamWithCommand() const = 0;
};

class $reply_base_class
{
};

class $request_base_class
{
};

class $send_receive_class {
public:
	virtual void Send(const $message_base_class &rObject) = 0;
	virtual std::auto_ptr<$message_base_class> Receive() = 0;
};

class $custom_protocol_subclass : public Protocol
{
public:
	$custom_protocol_subclass(std::auto_ptr<SocketStream> apConn)
	: Protocol(apConn)
	{ }
	virtual ~$custom_protocol_subclass() { }
	virtual std::auto_ptr<Message> MakeMessage(int ObjType);
	virtual const char *GetProtocolIdentString();

private:
	$custom_protocol_subclass(const $custom_protocol_subclass &rToCopy);
};

__E

print CPP <<__E;
std::auto_ptr<$message_base_class> $message_base_class\::DoCommand($replyable_base_class &rProtocol,
	$context_class &rContext) const
{
	THROW_EXCEPTION(ConnectionException, Protocol_TriedToExecuteReplyCommand)
}

std::auto_ptr<$message_base_class> $message_base_class\::DoCommand($replyable_base_class &rProtocol,
	$context_class &rContext, IOStream& rDataStream) const
{
	THROW_EXCEPTION(ConnectionException, Protocol_TriedToExecuteReplyCommand)
}
__E

my %cmd_classes;
my $error_message = undef;

# output the classes
foreach my $cmd (@cmd_list)
{
	my @cmd_base_classes = ($message_base_class);
	
	if(obj_is_type($cmd, 'Command'))
	{
		push @cmd_base_classes, $request_base_class;
	}
	
	if(obj_is_type($cmd, 'Reply'))
	{
		push @cmd_base_classes, $reply_base_class;
	}
	
	my $cmd_base_class = join(", ", map {"public $_"} @cmd_base_classes);
	my $cmd_class = $protocol_name."Protocol".$cmd;
	$cmd_classes{$cmd} = $cmd_class;
	
	print H <<__E;
class $cmd_class : $cmd_base_class
{
public:
	$cmd_class();
	$cmd_class(const $cmd_class &rToCopy);
	~$cmd_class();
	int GetType() const;
	enum
	{
		TypeID = $cmd_id{$cmd}
	};
__E

	# constants
	if(exists $cmd_constants{$cmd})
	{
		print H "\tenum\n\t{\n\t\t";
		print H join(",\n\t\t",@{$cmd_constants{$cmd}});
		print H "\n\t};\n";
	}
	
	# flags
	if(obj_is_type($cmd,'EndsConversation'))
	{
		print H "\tbool IsConversationEnd() const;\n";
	}
	
	if(obj_is_type($cmd,'IsError'))
	{
		$error_message = $cmd;
		my ($mem_type,$mem_subtype) = split /,/,obj_get_type_params($cmd,'IsError');
		my $error_type = $cmd_constants{"ErrorType"};
		print H <<__E;
	$cmd_class(int SubType) : m$mem_type($error_type), m$mem_subtype(SubType) { }
	bool IsError(int &rTypeOut, int &rSubTypeOut) const;
	std::string GetMessage() const { return GetMessage(m$mem_subtype); };
	static std::string GetMessage(int subtype);
__E
	}
	
	my $has_stream = obj_is_type($cmd, 'StreamWithCommand');

	if(obj_is_type($cmd, 'Command') && $has_stream)
	{
		print H <<__E;
	std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol, 
		$context_class &rContext, IOStream& rDataStream) const; // IMPLEMENT THIS\n
	std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol,
		$context_class &rContext) const
	{
		THROW_EXCEPTION_MESSAGE(CommonException, Internal,
			"This command requires a stream parameter");
	}
__E
	}
	elsif(obj_is_type($cmd, 'Command') && !$has_stream)
	{
		print H <<__E;
	std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol, 
		$context_class &rContext) const; // IMPLEMENT THIS\n
	std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol,
		$context_class &rContext, IOStream& rDataStream) const
	{
		THROW_EXCEPTION_MESSAGE(CommonException, NotSupported,
			"This command requires no stream parameter");
	}
__E
	}

	print H <<__E;
	bool HasStreamWithCommand() const { return $has_stream; }
__E

	# want to be able to read from streams?
	print H "\tvoid SetPropertiesFromStreamData(Protocol &rProtocol);\n";
	
	# write Get functions
	for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
	{
		my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);
		
		print H "\t".translate_type_to_arg_type($ty)." Get$nm() {return m$nm;}\n";
	}

	my $param_con_args = '';
	# extra constructor?
	if($#{$cmd_contents{$cmd}} >= 0)
	{
		my @a;
		for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
		{
			my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);

			push @a,translate_type_to_arg_type($ty)." $nm";
		}		
		$param_con_args = join(', ',@a);
		print H "\t$cmd_class(".$param_con_args.");\n";
	}
	print H "\tvoid WritePropertiesToStreamData(Protocol &rProtocol) const;\n";
	# set functions
	for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
	{
		my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);
		
		print H "\tvoid Set$nm(".translate_type_to_arg_type($ty)." $nm) {m$nm = $nm;}\n";
	}
	
	print H "\tvirtual void LogSysLog(const char *Action) const;\n";
	print H "\tvirtual void LogFile(const char *Action, FILE *file) const;\n";
	print H "\tvirtual std::string ToString() const;\n";
	
	# write member variables and setup for cpp file
	my @def_constructor_list;
	my @copy_constructor_list;
	my @param_constructor_list;
	
	print H "private:\n";
	for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
	{
		my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);

		print H "\t".translate_type_to_member_type($ty)." m$nm;\n";
		
		my ($basic,$typename) = translate_type($ty);
		if($basic)
		{
			push @def_constructor_list, "m$nm(0)";
		}
		push @copy_constructor_list, "m$nm(rToCopy.m$nm)";
		push @param_constructor_list, "m$nm($nm)";
	}
	
	# finish off
	print H "};\n\n";
	
	# now the cpp file...
	my $def_con_vars = join(",\n\t  ",@def_constructor_list);
	$def_con_vars = "\n\t: ".$def_con_vars if $def_con_vars ne '';
	my $copy_con_vars = join(",\n\t  ",@copy_constructor_list);
	$copy_con_vars = "\n\t: ".$copy_con_vars if $copy_con_vars ne '';
	my $param_con_vars = join(",\n\t  ",@param_constructor_list);
	$param_con_vars = "\n\t: ".$param_con_vars if $param_con_vars ne '';

	print CPP <<__E;
$cmd_class\::$cmd_class()$def_con_vars
{
}
$cmd_class\::$cmd_class(const $cmd_class &rToCopy)$copy_con_vars
{
}
$cmd_class\::~$cmd_class()
{
}
int $cmd_class\::GetType() const
{
	return $cmd_id{$cmd};
}
__E
	print CPP "void $cmd_class\::SetPropertiesFromStreamData(Protocol &rProtocol)\n{\n";
	for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
	{
		my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);
		if($ty =~ m/\Avector/)
		{
			print CPP "\trProtocol.ReadVector(m$nm);\n";
		}
		else
		{
			print CPP "\trProtocol.Read(m$nm);\n";
		}
	}
	print CPP "}\n";

	# implement extra constructor?
	if($param_con_vars ne '')
	{
		print CPP "$cmd_class\::$cmd_class($param_con_args)$param_con_vars\n{\n}\n";
	}
	print CPP "void $cmd_class\::WritePropertiesToStreamData(Protocol &rProtocol) const\n{\n";
	for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
	{
		my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);
		if($ty =~ m/\Avector/)
		{
			print CPP "\trProtocol.WriteVector(m$nm);\n";
		}
		else
		{
			print CPP "\trProtocol.Write(m$nm);\n";
		}
	}
	print CPP "}\n";

	if(obj_is_type($cmd,'EndsConversation'))
	{
		print CPP "bool $cmd_class\::IsConversationEnd() const\n{\n\treturn true;\n}\n";
	}
	
	if(obj_is_type($cmd,'IsError'))
	{
		# get parameters
		my ($mem_type,$mem_subtype) = split /,/,obj_get_type_params($cmd,'IsError');
		print CPP <<__E;
bool $cmd_class\::IsError(int &rTypeOut, int &rSubTypeOut) const
{
	rTypeOut = m$mem_type;
	rSubTypeOut = m$mem_subtype;
	return true;
}
std::string $cmd_class\::GetMessage(int subtype)
{
	switch(subtype)
	{
__E
		foreach my $const (@{$cmd_constants{$cmd}})
		{
			next unless $const =~ /^Err_(.*)/;
			my $shortname = $1;
			$const =~ s/ = .*//;
			print CPP <<__E;
		case $const: return "$shortname";
__E
		}
		print CPP <<__E;
		default:
			std::ostringstream out;
			out << "Unknown subtype " << subtype;
			return out.str();
	}
}
__E
	}

	my ($log) = make_log_strings_framework($cmd);
	print CPP <<__E;
std::string $cmd_class\::ToString() const
{
	std::ostringstream oss;
	try
	{
		oss << $log;
	}
	catch(std::exception &e)
	{
		oss << "Failed to log command: " << e.what();
	}
	return oss.str();
}
void $cmd_class\::LogSysLog(const char *Action) const
{
	try
	{
		BOX_TRACE(Action << " " << $log);
	}
	catch(std::exception &e)
	{
		BOX_WARNING("Failed to log command: " << Action << ": " <<
			e.what());
	}
}
void $cmd_class\::LogFile(const char *Action, FILE *File) const
{
	::fprintf(File, "%s %s\\n", Action, ToString().c_str());
	::fflush(File);
}
__E
}

my $error_class = $protocol_name."ProtocolError";

# the abstract protocol interface
print H <<__E;

class $client_server_base_class
{
public:
	$client_server_base_class();
	virtual ~$client_server_base_class();
	virtual std::auto_ptr<IOStream> ReceiveStream() = 0;
	bool GetLastError(int &rTypeOut, int &rSubTypeOut);
	int GetLastErrorType() { return mLastErrorSubType; }

protected:
	void SetLastError(int Type, int SubType)
	{
		mLastErrorType = Type;
		mLastErrorSubType = SubType;
	}
	std::string mPreviousCommand;
	std::string mPreviousReply;

private:
	$client_server_base_class(const $client_server_base_class &rToCopy); /* do not call */
	int mLastErrorType;
	int mLastErrorSubType;
};

class $replyable_base_class : public virtual $client_server_base_class
{
public:
	$replyable_base_class() { }
	virtual ~$replyable_base_class();

	virtual int GetTimeout() = 0;
	void SendStreamAfterCommand(std::auto_ptr<IOStream> apStream);

protected:
	std::list<IOStream*> mStreamsToSend;
	void DeleteStreamsToSend();
	virtual std::auto_ptr<$message_base_class> HandleException(BoxException& e) const;

private:
	$replyable_base_class(const $replyable_base_class &rToCopy); /* do not call */
};

__E

print CPP <<__E;
$client_server_base_class\::$client_server_base_class()
: mLastErrorType(Protocol::NoError),
  mLastErrorSubType(Protocol::NoError)
{ }

$client_server_base_class\::~$client_server_base_class()
{ }

const char *$custom_protocol_subclass\::GetProtocolIdentString()
{
	return "$ident_string";
}

std::auto_ptr<Message> $custom_protocol_subclass\::MakeMessage(int ObjType)
{
	switch(ObjType)
	{
__E

# do objects within this
for my $cmd (@cmd_list)
{
	print CPP <<__E;
	case $cmd_id{$cmd}:
		return std::auto_ptr<Message>(new $cmd_classes{$cmd}());
		break;
__E
}

print CPP <<__E;
	default:
		THROW_EXCEPTION(ConnectionException, Protocol_UnknownCommandRecieved)
	}
}

$replyable_base_class\::~$replyable_base_class()
{
	// If there were any streams left over, there's no longer any way to
	// access them, and we're responsible for them, so we'd better delete them.
	DeleteStreamsToSend();
}

void $replyable_base_class\::SendStreamAfterCommand(std::auto_ptr<IOStream> apStream)
{
	ASSERT(apStream.get() != NULL);
	mStreamsToSend.push_back(apStream.release());
}

void $replyable_base_class\::DeleteStreamsToSend()
{
	for(std::list<IOStream*>::iterator i(mStreamsToSend.begin()); i != mStreamsToSend.end(); ++i)
	{
		delete (*i);
	}

	mStreamsToSend.clear();
}

void $callable_base_class\::CheckReply(const std::string& requestCommandName,
	const $message_base_class &rCommand, const $message_base_class &rReply,
	int expectedType)
{
	if(rReply.GetType() == expectedType)
	{
		// Correct response, do nothing
		SetLastError(Protocol::NoError, Protocol::NoError);
	}
	else
	{
		// Set protocol error
		int type, subType;
		
		if(rReply.IsError(type, subType))
		{
			SetLastError(type, subType);
			THROW_EXCEPTION_MESSAGE(ConnectionException,
				Protocol_UnexpectedReply,
				requestCommandName << " command failed: "
				"received error " <<
				(($error_class&)rReply).GetMessage());
		}
		else
		{
			SetLastError(Protocol::UnknownError, Protocol::UnknownError);
			THROW_EXCEPTION_MESSAGE(ConnectionException,
				Protocol_UnexpectedReply,
				requestCommandName << " command failed: "
				"received unexpected response type " <<
				rReply.GetType());
		}
	}

	// As a client, if we get an unexpected reply later, we'll want to know
	// the last command that we executed, and the reply, to help debug the
	// server.
	mPreviousCommand = rCommand.ToString();
	mPreviousReply = rReply.ToString();
}

// --------------------------------------------------------------------------
//
// Function
//		Name:    Protocol::GetLastError(int &, int &)
//		Purpose: Returns true if there was an error, and type and subtype if there was.
//		Created: 2003/08/19
//
// --------------------------------------------------------------------------
bool $client_server_base_class\::GetLastError(int &rTypeOut, int &rSubTypeOut)
{
	if(mLastErrorType == Protocol::NoError)
	{
		// no error.
		return false;
	}
	
	// Return type and subtype in args
	rTypeOut = mLastErrorType;
	rSubTypeOut = mLastErrorSubType;
	
	// and unset them
	mLastErrorType = Protocol::NoError;
	mLastErrorSubType = Protocol::NoError;
	
	return true;
}

__E

# the callable protocol interface (implemented by Client and Local classes)
# with Query methods that don't take a context parameter
print H <<__E;
class $callable_base_class : public virtual $client_server_base_class,
	public $send_receive_class
{
public:
	virtual int GetTimeout() = 0;

protected:
	void CheckReply(const std::string& requestCommandName,
		const $message_base_class &rCommand, 
		const $message_base_class &rReply, int expectedType);

public:
__E

# add plain object taking query functions
my $with_params;
for my $cmd (@cmd_list)
{
	if(obj_is_type($cmd,'Command'))
	{
		my $has_stream = obj_is_type($cmd,'StreamWithCommand');
		my $argextra = $has_stream?', std::auto_ptr<IOStream> apStream':'';
		my $queryextra = $has_stream?', apStream':'';
		my $request_class = $cmd_classes{$cmd};
		my $reply_class = $cmd_classes{obj_get_type_params($cmd,'Command')};
		
		print H "\tvirtual std::auto_ptr<$reply_class> Query(const $request_class &rQuery$argextra) = 0;\n";
		my @a;
		my @na;
		for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
		{
			my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);
			push @a,translate_type_to_arg_type($ty)." $nm";
			push @na,"$nm";
		}
		my $ar = join(', ',@a);
		my $nar = join(', ',@na);
		$nar = "($nar)" if $nar ne '';

		$with_params .= <<__E;
	inline std::auto_ptr<$reply_class> Query$cmd($ar$argextra)
	{
		$request_class send$nar;
		return Query(send$queryextra);
	}
__E
	}
}

# quick hack to correct bad argument lists for commands with zero parameters but with streams
$with_params =~ s/\(, /(/g;

print H <<__E;

$with_params
};
__E

# standard remote protocol objects
foreach my $type ('Client', 'Server', 'Local')
{
	my $writing_client = ($type eq 'Client');
	my $writing_server = ($type eq 'Server');
	my $writing_local  = ($type eq 'Local');
			
	my $server_or_client_class = $protocol_name."Protocol".$type;
	my @base_classes;

	if (not $writing_client)
	{
		push @base_classes, $replyable_base_class;
	}

	if (not $writing_server)
	{
		push @base_classes, $callable_base_class;
	}

	if (not $writing_local)
	{
		push @base_classes, $custom_protocol_subclass;
	}

	my $base_classes_str = join(", ", map {"public $_"} @base_classes);

	print H <<__E;
class $server_or_client_class : $base_classes_str
{
public:
	virtual ~$server_or_client_class();
__E

	if($writing_local)
	{
		print H <<__E;
	$server_or_client_class($context_class &rContext);
__E
	}

	print H <<__E;
	$server_or_client_class(std::auto_ptr<SocketStream> apConn);
	std::auto_ptr<$message_base_class> Receive();
	void Send(const $message_base_class &rObject);
__E

	if($writing_server)
	{
		# need to put in the conversation function
		print H <<__E;	
	void DoServer($context_class &rContext);

__E
	}

	if($writing_client or $writing_local)
	{
		# add plain object taking query functions
		for my $cmd (@cmd_list)
		{
			if(obj_is_type($cmd,'Command'))
			{
				my $has_stream = obj_is_type($cmd,'StreamWithCommand');
				my $argextra = $has_stream?', std::auto_ptr<IOStream> apStream':'';
				my $queryextra = $has_stream?', apStream':'';
				my $request_class = $cmd_classes{$cmd};
				my $reply_class = $cmd_classes{obj_get_type_params($cmd,'Command')};
				print H "\tstd::auto_ptr<$reply_class> Query(const $request_class &rQuery$argextra);\n";
			}
		}
	}

	if($writing_local)
	{
		print H <<__E;
private:
	$context_class &mrContext;
	std::auto_ptr<$message_base_class> mapLastReply;
public:
	virtual std::auto_ptr<IOStream> ReceiveStream()
	{
		if(mStreamsToSend.empty())
		{
			THROW_EXCEPTION_MESSAGE(CommonException, Internal,
				"Tried to ReceiveStream when none was sent or "
				"made available");
		}

		std::auto_ptr<IOStream> apStream(mStreamsToSend.front());
		mStreamsToSend.pop_front();
		return apStream;
	}
__E
	}
	else
	{
		print H <<__E;
	virtual std::auto_ptr<IOStream> ReceiveStream();
__E

		print CPP <<__E;
std::auto_ptr<IOStream> $server_or_client_class\::ReceiveStream()
{
	try
	{
		return $custom_protocol_subclass\::ReceiveStream();
	}
	catch(ConnectionException &e)
	{
		if(e.GetSubType() == ConnectionException::Protocol_ObjWhenStreamExpected)
		{
			THROW_EXCEPTION_MESSAGE(ConnectionException,
				Protocol_ObjWhenStreamExpected,
				"Last exchange was " << mPreviousCommand <<
				" => " << mPreviousReply);
		}
		else
		{
			throw;
		}
	}
}
__E
	}

	if($writing_local)
	{
		print H <<__E;
	virtual int GetTimeout()
	{
		return IOStream::TimeOutInfinite;
	}
__E
	}
	else
	{
		print H <<__E;
	virtual int GetTimeout()
	{
		return $custom_protocol_subclass\::GetTimeout();
	}
__E
	}	

	print H <<__E;

private:
	$server_or_client_class(const $server_or_client_class &rToCopy); /* no copies */
};

__E

	my $destructor_extra = ($writing_server) ? "\n\tDeleteStreamsToSend();"
		: '';

	if($writing_local)
	{
		print CPP <<__E;
$server_or_client_class\::$server_or_client_class($context_class &rContext)
: mrContext(rContext)
{ }
__E
	}
	else
	{
		print CPP <<__E;
$server_or_client_class\::$server_or_client_class(std::auto_ptr<SocketStream> apConn)
: $custom_protocol_subclass(apConn)
{ }
__E
	}
	
	print CPP <<__E;
$server_or_client_class\::~$server_or_client_class()
{$destructor_extra
}
__E

	# write receive and send functions
	if($writing_local)
	{
		print CPP <<__E;
std::auto_ptr<$message_base_class> $server_or_client_class\::Receive()
{
	return mapLastReply;
}
void $server_or_client_class\::Send(const $message_base_class &rObject)
{
	mapLastReply = rObject.DoCommand(*this, mrContext);
}
__E
	}
	else
	{
		print CPP <<__E;
std::auto_ptr<$message_base_class> $server_or_client_class\::Receive()
{
	std::auto_ptr<$message_base_class> apReply;

	try
	{
		apReply = std::auto_ptr<$message_base_class>(
			static_cast<$message_base_class *>
			($custom_protocol_subclass\::ReceiveInternal().release()));
	}
	catch(ConnectionException &e)
	{
		if(e.GetSubType() == ConnectionException::Protocol_StreamWhenObjExpected)
		{
			THROW_EXCEPTION_MESSAGE(ConnectionException,
				Protocol_StreamWhenObjExpected,
				"Last exchange was " << mPreviousCommand <<
				" => " << mPreviousReply);
		}
		else
		{
			throw;
		}
	}

	if(GetLogToSysLog())
	{
		apReply->LogSysLog("Receive");
	}

	if(GetLogToFile() != 0)
	{
		apReply->LogFile("Receive", GetLogToFile());
	}

	return apReply;
}

void $server_or_client_class\::Send(const $message_base_class &rObject)
{
	if(GetLogToSysLog())
	{
		rObject.LogSysLog("Send");
	}

	if(GetLogToFile() != 0)
	{
		rObject.LogFile("Send", GetLogToFile());
	}

	Protocol::SendInternal(rObject);
}

__E
	}
	
	# write server function?
	if($writing_server)
	{
		print CPP <<__E;
void $server_or_client_class\::DoServer($context_class &rContext)
{
	// Handshake with client
	Handshake();

	// Command processing loop
	bool inProgress = true;
	while(inProgress)
	{
		// Get an object from the conversation
		std::auto_ptr<$message_base_class> pobj = Receive();
		std::auto_ptr<$message_base_class> preply;

		// Run the command
		try
		{
			try
			{
				if(pobj->HasStreamWithCommand())
				{
					std::auto_ptr<IOStream> apDataStream = ReceiveStream();
					SelfFlushingStream autoflush(*apDataStream);
					preply = pobj->DoCommand(*this, rContext, *apDataStream);
				}
				else
				{
					preply = pobj->DoCommand(*this, rContext);
				}
			}
			catch(BoxException &e)
			{
				// First try a the built-in exception handler
				preply = HandleException(e);
			}
		}
		catch (...)
		{
			// Fallback in case the exception isn't a BoxException
			// or the exception handler fails as well. This path
			// throws the exception upwards, killing the process
			// that handles the current client.
			Send($cmd_classes{$error_message}(-1));
			throw;
		}

		// Send the reply
		Send(*preply);

		// Send any streams
		for(std::list<IOStream*>::iterator
			i =  mStreamsToSend.begin();
			i != mStreamsToSend.end(); ++i)
		{
			SendStream(**i);
		}

		// As a server, if we get an unexpected message later, we'll
		// want to know the last command that we received, and the
		// reply, to help debug our response to it.
		mPreviousCommand = pobj->ToString();
		std::ostringstream reply;
		reply << preply->ToString() << " and " <<
			mStreamsToSend.size() << " streams";
		mPreviousReply = reply.str();

		// Delete these streams
		DeleteStreamsToSend();

		// Does this end the conversation?
		if(pobj->IsConversationEnd())
		{
			inProgress = false;
		}
	}
}

__E
	}

	# write client Query functions?
	if($writing_client or $writing_local)
	{
		for my $cmd (@cmd_list)
		{
			if(obj_is_type($cmd,'Command'))
			{
				my $request_class = $cmd_classes{$cmd};
				my $reply_msg = obj_get_type_params($cmd,'Command');
				my $reply_class = $cmd_classes{$reply_msg};
				my $reply_id = $cmd_id{$reply_msg};
				my $has_stream = obj_is_type($cmd,'StreamWithCommand');
				my $argextra = $has_stream?', std::auto_ptr<IOStream> apDataStream':'';
				my $send_stream_extra = '';

				print CPP <<__E;
std::auto_ptr<$reply_class> $server_or_client_class\::Query(const $request_class &rQuery$argextra)
{
__E

				if($writing_client)
				{
					if($has_stream)
					{
						$send_stream_extra = <<__E;
	// Send stream after the command
	try
	{
		SendStream(*apDataStream);
	}
	catch (BoxException &e)
	{
		BOX_WARNING("Failed to send stream after command: " <<
			rQuery.ToString() << ": " << e.what());
		throw;
	}
__E
					}
					
					print CPP <<__E;
	// Send query
	Send(rQuery);
$send_stream_extra
	
	// Wait for the reply
	std::auto_ptr<$message_base_class> apReply = Receive();
__E
				}
				elsif($writing_local)
				{
					print CPP <<__E;
	std::auto_ptr<$message_base_class> apReply;
	try
	{
__E
					if($has_stream)
					{
						print CPP <<__E;
		apReply = rQuery.DoCommand(*this, mrContext, *apDataStream);
__E
					}
					else
					{
						print CPP <<__E;
		apReply = rQuery.DoCommand(*this, mrContext);
__E
					}

					print CPP <<__E;
	}
	catch(BoxException &e)
	{
		// First try a the built-in exception handler
		apReply = HandleException(e);
	}
__E
				}

				# Common to both client and local
				print CPP <<__E;
	CheckReply("$cmd", rQuery, *apReply, $reply_id);

	// Correct response, if no exception thrown by CheckReply
	return std::auto_ptr<$reply_class>(
		static_cast<$reply_class *>(apReply.release()));
}
__E
			}
		}
	}
}

print H <<__E;
#endif // $guardname

__E

# close files
close H;
close CPP;

sub obj_is_type ($$)
{
	my ($c,$ty) = @_;
	for(@{$cmd_attributes{$c}})
	{
		return 1 if $_ =~ m/\A$ty/;
	}
	
	return 0;
}

sub obj_get_type_params
{
	my ($c,$ty) = @_;
	for(@{$cmd_attributes{$c}})
	{
		return $1 if $_ =~ m/\A$ty\((.+?)\)\Z/;
	}
	die "Can't find attribute $ty on command $c\n"
}

# returns (is basic type, typename)
sub translate_type
{
	my $ty = $_[0];
	
	if($ty =~ m/\Avector\<(.+?)\>\Z/)
	{
		my $v_type = $1;
		my (undef,$v_ty) = translate_type($v_type);
		return (0, 'std::vector<'.$v_ty.'>')
	}
	else
	{
		if(!exists $translate_type_info{$ty})
		{
			die "Don't know about type name $ty\n";
		}
		return @{$translate_type_info{$ty}}
	}
}

sub translate_type_to_arg_type
{
	my ($basic,$typename) = translate_type(@_);
	return $basic?$typename:'const '.$typename.'&'
}

sub translate_type_to_member_type
{
	my ($basic,$typename) = translate_type(@_);
	return $typename
}

sub make_log_strings_framework
{
	my ($cmd) = @_;

	my @args;

	for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2)
	{
		my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]);
		
		if(exists $log_display_types{$ty})
		{
			# need to translate it
			my ($format,$arg) = @{$log_display_types{$ty}};
			$arg =~ s/VAR/m$nm/g;

			if ($format eq '"%s"')
			{
				$arg = "\"\\\"\" << $arg << \"\\\"\"";
			}
			elsif ($format =~ m'x$')
			{
				# my $width = 0;
				# $ty =~ /^int(\d+)$/ and $width = $1 / 4;
				$arg = 	"($arg == 0 ? \"0x\" : \"\") " .
					"<< std::hex " .
					"<< std::showbase " .
					# "<< std::setw($width) " .
					# "<< std::setfill('0') " .
					# "<< std::internal " .
					"<< $arg " .
					"<< std::dec";
			}

			push @args, $arg;
		}
		else
		{
			# is opaque
			push @args, '"OPAQUE"';
		}
	}

	my $log_cmd = '"'.$cmd.'(" ';
	foreach my $arg (@args)
	{
		$arg = "<< $arg ";
	}
	$log_cmd .= join('<< "," ',@args);
	$log_cmd .= '<< ")"';
	return $log_cmd;
}

